package data.analysis;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import data.Cell;
import data.Cluster;
import data.Focus;
import data.Group;
import data.Slide;

public class Analyzer {
	public interface Filter<T> {
		public boolean check(T t);
	}
	
	private Group g;
	private boolean analyzed;
	private long time;
	private List<Cell> filtered;
	
	public Analyzer(Group g) {
		this.g = g;
		this.analyzed = false;
		this.time = 0;
		this.filtered = new ArrayList<Cell>();
	}
	
	public void runAnalysis(Filter<Cluster> rs) {
		long start = System.currentTimeMillis();
		for (Slide s : g.getSlides()) {
			for (Cell c : s.getCells()) {
				for (Cluster cl : c.getClusters()) {
					if (rs.check(cl)) {
						registerFocus(cl);
					}
				}
			}
		}
		time = System.currentTimeMillis() - start;
		analyzed = true;
	}
	
	public void applyCellFilter(Filter<Cell> filter) {
		for (Slide s : g.getSlides()) {
			Iterator<Cell> i = s.getCells().iterator();
			while (i.hasNext()) {
				Cell current = i.next();
				if (!filter.check(current)) {
					filtered.add(current);
					i.remove();
				}
			}
		}
	}
	
	protected void registerFocus(Cluster cl) {
		cl.getParent().addFocus(new Focus(cl));
	}
	
	public Results getResults() {
		return new Results();
	}
	
	public class Results {
		public Analyzer getAnalyzer() {
			return Analyzer.this;
		}
		
		public Group getGroup() {
			return g;
		}
		
		public boolean isAnalysisDone() {
			return analyzed;
		}
		
		public long getTime() {
			if (!isAnalysisDone()) {
				throw new UnsupportedOperationException("Analysis not completed.");
			}
			return time;
		}
		
		public List<Cell> getFilteredCells() {
			return filtered;
		}
		
		public List<Cell> getCells() {
			ArrayList<Cell> cells = new ArrayList<Cell>(countCells());
			for (Slide s : g.getSlides()) {
				cells.addAll(s.getCells());
			}
			return cells;
		}
		
		public List<Cluster> getClusters() {
			List<Cluster> clusters = new ArrayList<Cluster>();
			for (Slide s : g.getSlides()) {
				for (Cell c : s.getCells()) {
					clusters.addAll(c.getClusters());
				}
			}
			return clusters;
		}
		
		public Set<Focus> getFoci() {
			Set<Focus> foci = new HashSet<Focus>(countFoci());
			for (Slide s : g.getSlides()) {
				for (Cell c : s.getCells()) {
					foci.addAll(c.getFoci());
				}
			}
			return foci;
		}
		
		public Set<Focus> getCytoplasmicFoci() {
			Set<Focus> foci = new HashSet<Focus>(countFoci());
			for (Slide s : g.getSlides()) {
				for (Cell c : s.getCells()) {
					foci.addAll(c.getCytoplasmicFoci());
				}
			}
			return foci;
		}
		
		public Set<Focus> getMembraneFoci() {
			Set<Focus> foci = new HashSet<Focus>(countFoci());
			for (Slide s : g.getSlides()) {
				for (Cell c : s.getCells()) {
					foci.addAll(c.getMembraneFoci());
				}
			}
			return foci;
		}
		
		public int countSlides() {
			return g.getSlides().size();
		}
		
		public int countCells() {
			int count = 0;
			for (Slide s : g.getSlides()) {
				count += s.getCells().size();
			}
			return count;
		}
		
		public int countCells(Filter<Cell> crit) {
			int count = 0;
			for (Slide s : g.getSlides()) {
				for (Cell c : s.getCells()) {
					if (crit.check(c)) {
						++count;
					}
				}
			}
			return count;
		}
		
		public int countLabels() {
			int count = 0;
			for (Slide s : g.getSlides()) {
				for (Cell c : s.getCells()) {
					for (Cluster cluster : c.getClusters()) {
						count += cluster.getNumLabels();
					}
				}
			}
			return count;
		}
		
		public int countClusters() {
			int count = 0;
			for (Slide s : g.getSlides()) {
				for (Cell c : s.getCells()) {
					count += c.getClusters().size();
				}
			}
			return count;
		}
		
		public int countMembraneClusters() {
			int count = 0;
			for (Slide s : g.getSlides()) {
				for (Cell c : s.getCells()) {
					count += c.getMembraneClusters().size();
				}
			}
			return count;
		}
		
		public int countCytoplasmicClusters() {
			int count = 0;
			for (Slide s : g.getSlides()) {
				for (Cell c : s.getCells()) {
					count += c.getCytoplasmicClusters().size();
				}
			}
			return count;
		}
		
		public int countFoci() {
			return countFoci(new Filter<Cell>() {
				@Override
				public boolean check(Cell t) {
					return true;
				}
			});
		}
		
		public int countFoci(Filter<Cell> cellCrit) {
			int count = 0;
			for (Slide s : g.getSlides()) {
				for (Cell c : s.getCells()) {
					if (cellCrit.check(c)) {
						count += c.getFoci().size();
					}
				}
			}
			return count;
		}
		
		public int countMembraneFoci() {
			return countMembraneFoci(new Filter<Cell>() {
				@Override
				public boolean check(Cell t) {
					return true;
				}
			});
		}
		
		public int countMembraneFoci(Filter<Cell> cellCrit) {
			int count = 0;
			for (Slide s : g.getSlides()) {
				for (Cell c : s.getCells()) {
					if (cellCrit.check(c)) {
						count += c.getMembraneFoci().size();
					}
				}
			}
			return count;
		}
		
		public int countCytoplasmicFoci() {
			return countCytoplasmicFoci(new Filter<Cell>() {
				@Override
				public boolean check(Cell t) {
					return true;
				}
			});
		}
		
		public int countCytoplasmicFoci(Filter<Cell> cellCrit) {
			int count = 0;
			for (Slide s : g.getSlides()) {
				for (Cell c : s.getCells()) {
					if (cellCrit.check(c)) {
						count += c.getCytoplasmicFoci().size();
					}
				}
			}
			return count;
		}
		
		public int countFocalCells() {
			return countCells(new Filter<Cell>() {
				@Override
				public boolean check(Cell t) {
					return t.getFoci().size() > 0;
				}
			});
		}
		
		public int countMembraneFocalCells() {
			return countCells(new Filter<Cell>() {
				@Override
				public boolean check(Cell t) {
					return t.getMembraneFoci().size() > 0;
				}
			});
		}
		
		public int countCytoplasmicFocalCells() {
			return countCells(new Filter<Cell>() {
				@Override
				public boolean check(Cell t) {
					return t.getCytoplasmicFoci().size() > 0;
				}
			});
		}
		
		public int countNonFocalCells() {
			return countCells(new Filter<Cell>() {
				@Override
				public boolean check(Cell t) {
					return t.getFoci().size() == 0;
				}
			});
		}
		
		public double fractionFocalCells() {
			return (double)countFocalCells() / countCells();
		}
		
		public double fractionNonFocalCells() {
			return 1 - fractionFocalCells();
		}
		
		public double meanFociPerCell() {
			return (double)countFoci() / countCells();
		}
		
		public double medianFociPerCell() {
			Cell[] cells = getCells().toArray(new Cell[0]);
			Arrays.sort(cells, new Comparator<Cell>() {
				@Override
				public int compare(Cell arg0, Cell arg1) {
					return arg0.getFoci().size() - arg1.getFoci().size();
				}
			});
			if (cells.length % 2 == 0) {
				return (cells[cells.length / 2 - 1].getFoci().size() + cells[cells.length / 2].getFoci().size()) / 2;
			} else {
				return cells[cells.length / 2].getFoci().size();
			}
		}
	}
}
